package own.stu.jobgib.playown.fsm.easyStates.core;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import own.stu.jobgib.playown.fsm.easyStates.api.*;

import java.util.HashSet;
import java.util.Set;

@Slf4j
@Getter
class FiniteStateMachineImpl implements FiniteStateMachine {

    // private static final Logger LOGGER = Logger.getLogger(FiniteStateMachineImpl.class.getSimpleName());

    private State currentState;
    private final State initialState;
    private final Set<State> finalStates;
    private final Set<State> states;
    private final Set<Transition> transitions;
    private Event lastEvent;
    private Transition lastTransition;

    FiniteStateMachineImpl(final Set<State> states, final State initialState) {
        this.states = states;
        this.initialState = initialState;
        currentState = initialState;
        transitions = new HashSet<>();
        finalStates = new HashSet<>();
    }

    void registerFinalState(final State finalState) {
        this.finalStates.add(finalState);
    }

    void registerTransition(final Transition transition) {
        this.transitions.add(transition);
    }

    @Override
    public State fire(Event event) throws FiniteStateMachineException {
        if (!finalStates.isEmpty() && finalStates.contains(event)) {
            log.warn("FSM is in final state '{}', event {} is ignored.", currentState.getName(), event);
            return currentState;
        }

        if (event == null) {
            log.warn("Null event fired, FSM state unchanged");
            return currentState;
        }

        for (Transition transition : transitions) {
            boolean continueFlag =
                    currentState.equals(transition.getSourceState()) && //fsm is in the right state as expected by transition definition
                            transition.getEventType().equals(event.getClass()) && //fired event type is as expected by transition definition
                            states.contains(transition.getTargetState()); //target state is defined

            if (!continueFlag) continue;

            try {
                //perform action, if any
                if (transition.getEventHandler() != null) {
                    transition.getEventHandler().handle(event);
                }

                //transit to target state
                currentState = transition.getTargetState();

                //save last triggered event and transition
                lastEvent = event;
                lastTransition = transition;

                break;
            } catch (Exception e) {
                log.error("An exception occurred during handling event {} of transition {}", event, transition, e);
                throw new FiniteStateMachineException(transition, event, e);
            }
        }
        return currentState;
    }
}
